package com.atguigu.daijia.dispatch.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.atguigu.daijia.common.constant.RedisConstant;
import com.atguigu.daijia.common.execption.GuiguException;
import com.atguigu.daijia.common.result.ResultCodeEnum;
import com.atguigu.daijia.dispatch.mapper.OrderJobMapper;
import com.atguigu.daijia.dispatch.service.NewOrderService;
import com.atguigu.daijia.dispatch.xxl.client.XxlJobClient;
import com.atguigu.daijia.map.client.LocationFeignClient;
import com.atguigu.daijia.model.entity.dispatch.OrderJob;
import com.atguigu.daijia.model.enums.OrderStatus;
import com.atguigu.daijia.model.form.map.SearchNearByDriverForm;
import com.atguigu.daijia.model.vo.dispatch.NewOrderTaskVo;
import com.atguigu.daijia.model.vo.map.NearByDriverVo;
import com.atguigu.daijia.model.vo.order.NewOrderDataVo;
import com.atguigu.daijia.order.client.OrderInfoFeignClient;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
@RequiredArgsConstructor
public class NewOrderServiceImpl implements NewOrderService {

    private final XxlJobClient xxlJobClient;
    private final OrderJobMapper orderJobMapper;
    private final OrderInfoFeignClient orderInfoFeignClient;
    private final LocationFeignClient locationFeignClient;
    private final RedisTemplate redisTemplate;

    /**
     * 新增并启动新订单定时任务
     * @param newOrderTaskVo
     * @return
     */
    @Override
    public Long addAndStartTask(NewOrderTaskVo newOrderTaskVo) {
        // 判断是否已经为当前订单添加并启动过定时任务调度
        OrderJob orderJob = orderJobMapper.selectOne(new LambdaQueryWrapper<OrderJob>().eq(OrderJob::getOrderId, newOrderTaskVo.getOrderId()));
        if(orderJob == null) {
            // 新增并启动新订单定时任务
            // 每分钟执行一次任务，处理定时任务的方法名为 newOrderTaskHandler
            Long jobId = xxlJobClient.addAndStartJob("newOrderTaskHandler", "", "0 0/1 * * * ?", "为新订单开启定时任务，订单 ID：" + newOrderTaskVo.getOrderId());
            // 记录订单和定时任务的关联关系
            orderJob = new OrderJob();
            orderJob.setOrderId(newOrderTaskVo.getOrderId());
            orderJob.setJobId(jobId);
            orderJob.setParameter(JSON.toJSONString(newOrderTaskVo));
            orderJobMapper.insert(orderJob);
        }
        return orderJob.getJobId();
    }

    /**
     * 执行新订单定时任务的方法
     *
     * @param jobId
     */
    @Override
    public void executeNewOrderTask(long jobId) {
        // 1 查询任务信息
        OrderJob orderJob = orderJobMapper.selectOne(new LambdaQueryWrapper<OrderJob>().eq(OrderJob::getJobId, jobId));
        if (orderJob == null) {
            // 定时任务不存在，直接返回
            return;
        }
        // 解析定时任务参数，之前保存订单的定时任务到数据库时将封装了新订单信息的 NewOrderTaskVo 对象的 JSON 字符串作为定时任务参数
        NewOrderTaskVo newOrderTaskVo = JSONObject.parseObject(orderJob.getParameter(), NewOrderTaskVo.class);

        // 2 查询订单状态
        Integer orderStatus = orderInfoFeignClient.getOrderStatus(newOrderTaskVo.getOrderId()).getData();
        if (!orderStatus.equals(OrderStatus.WAITING_ACCEPT.getStatus())) {
            // 订单状态不是待接单，停止任务调度
            xxlJobClient.stopJob(jobId);
            log.info("订单状态不是待接单，停止定时任务调度，订单 ID：{}", newOrderTaskVo.getOrderId());
            return;
        }

        // 3 远程调用，查询附近可以接单的司机
        SearchNearByDriverForm searchNearByDriverForm = new SearchNearByDriverForm();
        searchNearByDriverForm.setLongitude(newOrderTaskVo.getStartPointLongitude());
        searchNearByDriverForm.setLatitude(newOrderTaskVo.getStartPointLatitude());
        searchNearByDriverForm.setMileageDistance(newOrderTaskVo.getExpectDistance());
        List<NearByDriverVo> nearByDriverList = locationFeignClient.searchNearByDriver(searchNearByDriverForm).getData();

        // 4 为每个附近可以接单的司机创建临时队列，发送新订单信息到司机的临时队列，通知司机接单
        if (nearByDriverList == null || nearByDriverList.isEmpty()) {
            log.info("附近没有可以接单的司机，停止定时任务调度，订单 ID：{}", newOrderTaskVo.getOrderId());
            xxlJobClient.stopJob(jobId);
            return;
        }

        nearByDriverList.forEach(nearByDriver -> {
            log.info("给司机发送消息，通知司机有新订单可以接单，司机 ID：{}，订单 ID：{}", nearByDriver.getDriverId(), newOrderTaskVo.getOrderId());
            // 使用 Redis 的 Set 数据类型保存发送了订单消息的司机 ID，防止重复推送消息
            String repeatKey = RedisConstant.DRIVER_ORDER_REPEAT_LIST + newOrderTaskVo.getOrderId();
            Boolean isMember = redisTemplate.opsForSet().isMember(repeatKey, nearByDriver.getDriverId());
            if (isMember == null) {
                throw new GuiguException(ResultCodeEnum.REDIS_ERROR);
            }
            if (!isMember) {
                // 将已经发送了新订单信息的司机 ID 添加到 Set 中
                redisTemplate.opsForSet().add(repeatKey, nearByDriver.getDriverId());
                // 过期时间 15 分钟，新订单 15 分钟没有司机接单，系统自动取消订单，自然也不需要再维护这个 Set
                redisTemplate.expire(repeatKey, RedisConstant.DRIVER_ORDER_REPEAT_LIST_EXPIRES_TIME, TimeUnit.MINUTES);

                // 将新订单信息封装成 NewOrderDataVo 对象
                NewOrderDataVo newOrderDataVo = new NewOrderDataVo();
                BeanUtils.copyProperties(newOrderTaskVo, newOrderDataVo);
                newOrderDataVo.setDistance(nearByDriver.getDistance());

                // 将新订单信息保存到司机的临时队列中，司机开始接单后会定时轮询他的队列，从而获取订单消息
                // 使用 Redis 的 List 数据类型作为司机的临时队列，天然支持 FIFO 特性
                // 入队：LPUSH：从列表的头部（左侧）添加一个或多个元素；RPUSH：从列表的尾部（右侧）添加一个或多个元素
                // 出队：LPOP：移除并返回列表的第一个（头部）元素；RPOP：移除并返回列表的最后一个（尾部）元素
                String key = RedisConstant.DRIVER_ORDER_TEMP_LIST + nearByDriver.getDriverId();
                redisTemplate.opsForList().leftPush(key, JSONObject.toJSONString(newOrderDataVo));
                // 过期时间为 1 分钟，一分钟内司机没有接单，该司机临时队列中的订单消息自动过期
                // 司机开始接单后，司机端微信小程序会每隔 5 秒获取司机临时队列中的消息
                redisTemplate.expire(key, RedisConstant.DRIVER_ORDER_TEMP_LIST_EXPIRES_TIME, TimeUnit.MINUTES);
                log.info("当前新订单已经添加到附近可以接单的司机的临时队列：{}", JSON.toJSONString(newOrderDataVo));
            }
        });
    }

    /**
     * 查询司机临时队列中的最新订单数据
     * @param driverId
     * @return
     */
    @Override
    public List<NewOrderDataVo> findNewOrderQueueData(Long driverId) {
        String key = RedisConstant.DRIVER_ORDER_TEMP_LIST + driverId;
        Long size = redisTemplate.opsForList().size(key);
        if (size == null || size == 0) {
            return null;
        }
        List<String> dataList = redisTemplate.opsForList().range(key, 0, -1);
        if (dataList != null && !dataList.isEmpty()) {
            return dataList.stream().map(data -> JSONObject.parseObject(data, NewOrderDataVo.class)).collect(Collectors.toList());
        }
        return null;
    }

    /**
     * 清空司机临时队列中的订单数据
     * @param driverId
     * @return
     */
    @Override
    public Boolean clearNewOrderQueueData(Long driverId) {
        String key = RedisConstant.DRIVER_ORDER_TEMP_LIST + driverId;
        return redisTemplate.delete(key);
    }
}
